/*
 * Copyright (C) 2013 Zmei:Zelen
 * Permission is hereby granted, free of charge, to any person 
 * obtaining a copy of this software and associated documentation files 
 * (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 * 
 */

var Application, Stasis;

var keyMap = {
	'32': 'space',
	'37': 'left',
	'38': 'up',
	'39': 'right',
	'40': 'down',
	'49': '1',
	'50': '2',
	'51': '3',
	'83': 's',	
}

var DEBUG = false;

/* namespace Stasis */
var Stasis = new function($) {
	
	this.each = function (context, collection, callback, interruptable) {		
		for (index in collection) {
			var item = collection[index];
			if (!callback.call(context, index, item) && interruptable) break;
		}
		return collection;
	}
	
	/*
	this.extend = function (classObject) {		
		var extended = function () { };										
		extended.prototype = classObject.prototype;		
		return new extended();
	}
	/**/
	
	//*
	this.extend = function () {
		var extended = function () {};		
		this.each(this, arguments, function(id, argument) {						
			this.each(this, argument.prototype, function(name, value) {				
				extended.prototype[name] = value;				
			});
		});
		return new extended();
	}
	/**/
	
	this.createSurface = function (width, height) {
		var canvas = $('<canvas/>')[0];
		canvas.width = width;
		canvas.height = height;
		return canvas.getContext('2d');
	}
	
	this.interval = function (context, time, callback) {		
		return window.setInterval(function () {
			callback.call(context);
		}, time);
	}
	
	this.timeout = function (context, time, callback) {
		return window.setTimeout(function () {
			callback.call(context);
		}, time);
	}

	/* class Object */
	this.Object = function () {		
		this.dependencies  = this.dependencies || [];		
	}

	this.Object.prototype.promise = function () {			
		return $.when.apply(this, this.dependencies);
	}	
	
	this.Object.prototype.ready = this.Object.prototype.promise;	

	this.Object.prototype.dependOn = function () {		
		Stasis.each(this, arguments, function(id, dependency) {
			if ($.isFunction(dependency.promise)) {
				this.dependencies.push(dependency);
			} else {		
				DEBUG && console.error('Incorrect object type', dependency)
				throw new Error('The dependOn method requires a Promise-like objects as arguments!');
			}
		});
	}
	/* end Object */	
	
	/* class Image */
	this.Image = function (url) {
		Stasis.Object.call(this);
		
		var imageLoaded = $.Deferred();
		
		this.dependOn(imageLoaded);
		this.image = this.image || new window.Image();				
		
		$(this.image).bind('load', function () { 
			DEBUG && console.log($(this).attr('src') + ' is OK.');
			imageLoaded.resolve(); 
		});
		$(this.image).bind('error', function () { 
			DEBUG && console.error($(this).attr('src') + ' download failed!');
			imageLoaded.reject(true); 
		});
		$(this.image).bind('abort', function () { 
			DEBUG && console.error($(this).attr('src') + ' download aborted!');
			imageLoaded.reject(false); 
		});			
		
		if (url) {
			this.location(url);
		}
	}
	
	this.Image.prototype = this.extend(this.Object);
	
	this.Image.prototype.save = function (element) {
		$(element).append(this.image);
	}
	
	this.Image.prototype.location = function (url) {
		$(this.image).attr('src', url);
	}
	/* end Image */
		
	/* class ObjectSet */
	this.ObjectSet = function () {
		Stasis.Object.call(this);		
		this.items = this.items || {};
	}
	
	this.ObjectSet.prototype = this.extend(this.Object);

	this.ObjectSet.prototype.add = function (name, object) {
		if ($.isFunction(object.promise) || object == null) {
			var id = this.dependencies.indexOf(this.items[name]);
			if ( id != -1) {
				this.dependencies.splice(id, 1);
			}
			
			this.items[name] = object;
			
			if (object) {
				this.dependencies.push(object);
			}
		} else {	
			throw new Error('StasisObject.link requires a Promise-like object or null as parameter!');
		}
	}
	/* end ObjectSet */
	
	/* class Graphics */
	this.Graphics = function () {
		Stasis.ObjectSet.call(this);
		this.x = this.x || 0;
		this.y = this.y || 0;			
		this.scale = this.scale || [1, 1];
		this.width = this.width || 0;
		this.height = this.height || 0;	
	}
	
	this.Graphics.prototype = this.extend(this.ObjectSet);
	
	this.Graphics.prototype.calibrate = function(surface, width, height) {	
		surface.translate(
			this.x + (surface.canvas.width - width) / 2,
			surface.canvas.height - this.y - height
		);
	}
	
	this.Graphics.prototype.drawFrame = function (surface) {		
		this.calibrate(surface, this.width, this.height);
		surface.strokeRect(0, 0, this.width, this.height);
		this.draw(surface);
	}	
	
	this.Graphics.prototype.draw = function (surface) {	
		with (surface) {						
			beginPath();
			lineWidth = 0.5;
			strokeStyle = '#000';
			fillStyle = '#fff';			
			rect(x - 25, y - 50, 50, 50);
			moveTo(x - 20, y - 40);
			lineTo(x + 20, y - 10);
			moveTo(x + 20, y - 40);
			lineTo(x - 20, y - 10);			
			fill();
			stroke();
		}
	}
	
	this.Graphics.prototype.nextFrame = function () {
		return null;
	}
	
	this.Graphics.prototype.skipFrame = function () {
		return this.nextFrame();
	}
	/* end Graphics */
	
	/* class StaticImage */
	this.StaticImage = function (url) {
		Stasis.Graphics.call(this);
		
		var _this = this;
		
		this.image = this.image || new Stasis.Image();
		
		this.dependOn(this.image);
		this.image.ready().then(
			function () {
				_this.width = _this.image.image.width;
				_this.height = _this.image.image.height;				
			}
		);
		
		this.image.location(url);
	}
	
	this.StaticImage.prototype = this.extend(this.Graphics);
	
	this.StaticImage.prototype.save = function (element) {
		this.image.save(element);
		return this;
	}
	
	this.StaticImage.prototype.draw = function (surface) {				
		with (surface) with (this.image) {
			drawImage(image, 
				0, 0, image.width, image.height, 
				0, 0, image.width, image.height
			);			
		}
		return true;
	}
	/* end StaticImage */
	
	/* class FrameSet */
	this.FrameSet = function () {
		Stasis.Object.call(this);		
		this.locked = this.locked || new $.Deferred();				
		this.images = this.images || {};
		this.frames = this.frames || {};
		this.animations = this.animations || {};
		this.width = this.width || 0;
		this.height = this.height || 0;
	}	
	
	this.FrameSet.prototype = this.extend(this.Object)
	
	this.FrameSet.prototype.addImage = function (name, src, cols, rows, frames) {
		if (this.locked.state() == 'resolved') {
			throw new Error('Can\'t add sheet images to activated FrameSet!');
		}
		var image = new Stasis.Image(src),
			imageInitialized = new $.Deferred(),
			_this = this;					
		this.images[name] = image;
		this.dependOn(imageInitialized);		
		
		image.ready().done(function () {
			image.frameWidth = Math.floor(image.image.width / cols);
			image.frameHeight = Math.floor(image.image.height / rows);			
			_this.width = Math.max(_this.width, image.frameWidth);
			_this.height = Math.max(_this.height, image.frameHeight);
			for (id in frames) {
				_this.frames[frames[id]] = {
					'x': (id % cols) * image.frameWidth,
					'y': Math.floor(id / cols) * image.frameHeight,
					'image': image
				}
			}
			imageInitialized.resolve();			
		});		
	}
	
	this.FrameSet.prototype.addAnimation = function (name, frameNumbers) {
		this.animations[name] = frameNumbers;
	}	
	
	this.FrameSet.prototype.lock = function () {
		var _this = this;
		this.promise().done(function () {
			_this.locked.resolve();
		});
	}
	/* end FrameSet */
	
	/* class Animatic */
	this.Animatic = function (frameSet) {
		Stasis.Graphics.call(this);
		var _this = this;
		
		this.frameSet = frameSet;	
		
		this.initialized = this.initialized || $.Deferred();
		this.sequence = this.sequence || [];
		this.loopSequence = this.loopSequence || 'idle';		
		
		this.dependOn(this.frameSet, this.initialized);
		this.frameSet.locked.then(function () {
			_this.width = _this.frameSet.width;
			_this.height = _this.frameSet.height;			
			_this.initialized.resolve();			
		});
	}
	
	this.Animatic.prototype = this.extend(this.Graphics);
	
	this.Animatic.prototype.drawFrame = function (surface) {
		var frame = this.nextFrame()		
		if (!frame) {			
			throw new Error('Broken frame!');
		};
		this.calibrate(surface, frame.image.frameWidth, frame.image.frameHeight);				
		this.draw(surface, frame);
	}
	
	this.Animatic.prototype.draw = function (surface, frame) {
			
		if (frame) with (surface) {	
			if (this.scale[0] < 0) translate(frame.image.frameWidth, 0);
			scale.apply(surface, this.scale);				
			drawImage(
				frame.image.image, 
				frame.x, frame.y, frame.image.frameWidth, frame.image.frameHeight, 
				0, 0, frame.image.frameWidth, frame.image.frameHeight
			);				
		}
		return true;
	}
	
	this.Animatic.prototype.nextFrame = function () {	
		if (!this.sequence.length) {			
			if (!this.loopSequence) {
				this.loopSequence = 'idle';				
			}
			this.play(this.loopSequence);
		}	
		
		var id = this.sequence.shift();		
		return (id == null) ? null : this.frameSet.frames[id];
	}
	
	this.Animatic.prototype.getSequence = function (name) {
		if (!(name in this.frameSet.animations)) {
			throw new Error('Animation "' + name + '" not found!');
		}
		var result = this.frameSet.animations[name];		
		return result ? result : [];		
	}
	
	this.Animatic.prototype.playSequence = function (sequence, playNext) {
		if (!(sequence instanceof Array)) {
			throw new Error('Sequence must be an Array of frame numbers!');
		}
		if (playNext) {
			Stasis.each(this, sequence, function (id, frame) {
				this.sequence.push(frame);
			});
		} else {
			this.sequence = sequence.slice();
		}
		return this;
	}
	
	this.Animatic.prototype.setLoop = function (sequence) {
		if (sequence instanceof Array) {
			this.loopSequence = sequence.slice();
		} else {
			this.loopSequence = sequence;
		}
	}
	
	this.Animatic.prototype.play = function (sequence, playNext) {				
		if (sequence instanceof Array) {
			return this.playSequence(sequence, playNext);
		} else {
			return this.playSequence(this.getSequence(sequence), playNext);		
		}
	}
	
	this.Animatic.prototype.playNext = function (name) {		
		return this.play(name, true);
	}	
	/* end Animatic */
	
	/* class Renderer */
	this.Renderer = function(surface) {
		Stasis.Object.call(this);
		if (surface) {
			this.setSurface(surface);
		}
	}
	
	this.Renderer.prototype = this.extend(this.Object);
	
	this.Renderer.prototype.setSurface = function (surface) {
		this.surface = surface;
		this.buffer = Stasis.createSurface(
			surface.canvas.width, 
			surface.canvas.height
		);			
	}
	
	this.Renderer.prototype.skipFrame = function (scene) {
		if (!this.ready().state() == 'resolved' || !this.surface) return false;		
		Stasis.each(this, scene, function(id, object) {
			if ($.isFunction(object.ready) && object.ready().state() == 'resolved') {				
				if ($.isFunction(object.skipFrame)) object.skipFrame();
			}
			if (object.items) {
				this.skipFrame(object.items);
			}
		});
	}
	
	this.Renderer.prototype.renderFrame = function (scene) {				
		if (!this.ready().state() == 'resolved' || !this.surface) return false;		
		Stasis.each(this, scene, function(id, object) {					
			if ($.isFunction(object.ready) && object.ready().state() == 'resolved') {
				if ($.isFunction(object.drawFrame)) {					
					this.buffer.save();						
					object.drawFrame(this.buffer);
					this.buffer.restore();
				}
				if (object.items) {
					this.renderFrame(object.items);
				}				
			}
		});		
		this.surface.drawImage(this.buffer.canvas, 0, 0);				
	}
	/* end Renderer */
	
	/* class GlitchenGraphics */	
	this.GlitchenGraphics = function (dataCache) {						
		Stasis.Animatic.call(this, new Stasis.FrameSet());		
		
		var data = JSON.parse($('.data', dataCache).html()),
			imageCache = $('.images', dataCache);
			
		Stasis.each(this, data.sheets, function(name, sheet) {
			this.frameSet.addImage(
				name, $('.i-' + name, imageCache).attr('src'),
				sheet.cols, sheet.rows, sheet.frames
			);
		});
				
		Stasis.each(this, data.anims, function(name, anim) {
			this.frameSet.addAnimation(name, anim);
		});
		
		var idleS = [];
		for (var i = 0; i < 33; i++) {
			idleS = idleS.concat(this.frameSet.animations.idle1[0]);
		}
		this.frameSet.animations['idleS'] = idleS;
		
		this.frameSet.animations['jumpOver_mid'] = this.frameSet.animations['jumpOver_fall'].slice(0,10);
				
		this.frameSet.lock();		
	}
	
	this.GlitchenGraphics.prototype = this.extend(this.Animatic);
	
	this.GlitchenGraphics.prototype.getIdleSequence = function () {	
		var idlesList = [		
				this.frameSet.animations.idle1, 
				this.frameSet.animations.idle2, 
				this.frameSet.animations.idle3, 
				this.frameSet.animations.idle4
			],			
			idle = [],
			n = Math.floor(Math.random() * 6) + 2;
				
		for (var i = 0; i < n; i++) { 
			idle = idle.concat(this.frameSet.animations.idleS);
		};		
		return idle.concat(idlesList[Math.floor(Math.random() * idlesList.length)]);
	}
	
	this.GlitchenGraphics.prototype.getSequence = function (name) {		
		if (name == 'idle') {
			return this.getIdleSequence();			
		} else {
			return Stasis.Animatic.prototype.getSequence.call(this, name);
		}
	}
	/* end GlitchenGraphics */
	
	/* class Engine */
	this.Engine = function () { 
		Stasis.Object.call(this);
		this.x = this.x || 0;
		this.y = this.y || 0;
	}
		
	this.Engine.prototype = this.extend(this.Object);
	
	this.Engine.prototype.update = function (geometry) { }
	/* end Engine */
	
	/* class Glitchen */
	this.Glitchen = function (dataCache) { 
		Stasis.Engine.call(this);		
		Stasis.GlitchenGraphics.call(this, dataCache);
		
		this.facing = this.facing || false;
		this.actions = this.actions || [];		
		this.state = this.state || {};
		
		this.animationLock = this.animationLock || false;
	}
		
	this.Glitchen.prototype = this.extend(this.GlitchenGraphics, this.Engine);
	
	// todo: set
	this.Glitchen.prototype.addAction = function (action) {
		if (!(this.actions.indexOf(action) > -1)) {
			this.actions.push(action);			
		}
		return this;
	}
	
	this.Glitchen.prototype.removeAction = function (action) {
		var id = this.actions.indexOf(action);
		if (id > -1) {
			this.actions.splice(id, 1);
		}
		return this;
	}
	
	this.Glitchen.prototype.hasAction = function (action) {
		return (this.actions.indexOf(action) > -1);
	}
	
	this.Glitchen.prototype.update = function (geometry) {
		
		if (this.hasAction('walking')) {
			
			if (this.facing && this.x >= (this.width - geometry.width) / 2 ) {
				this.x -= 2.5;	
			}
			
			if (!this.facing && this.x <= (geometry.width - this.width) / 2 ) {
				this.x += 2.5;	
			}
		}
		
		if (this.hasAction('jumping')) {
			
			var maxT = 35;
			
			var th = this.state.t - maxT / 2;
			var td = 0.05 * th * th;
			
			if (this.state.t < maxT / 2) {								
				this.y += td;
			} else {
				this.y -= td;
			}			
			
			if (this.state.t  == (maxT - 5)) {
				this.play('jumpOver_land');
			}			
			
			this.state.t += 1;
			
			if (this.state.t > maxT) {
				this.removeAction('jumping');				
				if (!this.hasAction('walking')) {
					this.animationLock = false;
					this.play('idle');
					this.setLoop('idle');
				} else {
					this.play('walk1x');
					this.setLoop('walk1x');
				}
			}
		}
	}
	
	this.Glitchen.prototype.walk = function(facing) {		
		if (facing != this.facing) {
			this.facing = facing;
			this.scale[0] *= -1;			
		}
		this.addAction('walking')
		if (!this.animationLock) {			
			this.animationLock = true;
			this.play('walk1x');
			this.setLoop('walk1x');
		}
	}
	
	this.Glitchen.prototype.walkLeft = function () {
		this.walk(true);		
	}
	
	this.Glitchen.prototype.walkRight = function () {
		this.walk(false);		
	}
	
	this.Glitchen.prototype.walkStop = function () {		
		this.removeAction('walking');		
		if (!this.hasAction('jumping')) {
			this.animationLock = false;
			this.play('idle');
			this.setLoop('idle');
		}
	}
	
	this.Glitchen.prototype.laugh = function () {
		if (!this.animationLock) {
			this.play('happy');
		}
	}
	
	this.Glitchen.prototype.rage = function () {
		if (!this.animationLock) {
			this.play('angry');
		}
	}
	
	this.Glitchen.prototype.wat = function () {
		if (!this.animationLock) {
			this.play('surprise');
		}
	}
	
	this.Glitchen.prototype.jump = function () {
		if (!this.hasAction('jumping')) {
			this.animationLock = true;		
			this.state = { t: 0 };
			this.play('jumpOver_lift').playNext('jumpOver_fall');
		}
		this.addAction('jumping');
	}
	/* end Glitchen */
	
	/* class Game */
	this.Game = function (scene, renderer, cps, fps) {
		Stasis.Object.call(this);
		this.updateTimeInterval = 1000 / cps;
		this.renderTimeInterval = 1000 / fps;
		this.scene = scene;
		this.renderer = renderer;
		
		this.geometry = {
			'width': 0,
			'height': 0
		}	
		
		var _this = this;
		var rendererInitialized = $.Deferred();	
		this.dependOn(rendererInitialized);		
		this.renderer.ready().done(function () {
			_this.geometry.width = renderer.surface.canvas.width;
			_this.geometry.height = renderer.surface.canvas.height;
			rendererInitialized.resolve();
		});		
		
		this.updateDelta = this.updateDelta || 0;		
		this.renderDelta = this.renderDelta || 0;				
		this.running = this.running || false;
		
		this.lastTime = 0;
	}
	
	this.Game.prototype = this.extend(this.Object);
	
	this.Game.prototype.time = function () {
		return Date.now();
	}	
	
	this.Game.prototype.start = function () {
		this.running = true;
		this.loop();
	}
	
	this.Game.prototype.stop = function () {
		this.running = false;
	}
	
	this.Game.prototype.update = function () {		
		Stasis.each(this, scene, function(id, object) {			
			if ($.isFunction(object.update)) {				
				object.update(this.geometry);			
			}
			if(object.items) {			
				//this.update(object.items);
			}
		});
	}
	
	this.Game.prototype.loop = function() {
		var currentTime = this.time();
		if (!this.lastTime) {
			this.lastTime = currentTime;			
		}
		var delta = (currentTime - this.lastTime);		
		this.lastTime = currentTime;		
		this.updateDelta += delta;
		this.renderDelta += delta;
		
		var passes = Math.floor(this.updateDelta / this.updateTimeInterval);
		this.updateDelta -= passes * this.updateTimeInterval;		
				
		for (var i = 0; i < passes; i++) {			
			this.update(this.scene);			
		}
		
		passes = Math.floor(this.renderDelta / this.renderTimeInterval);
		this.renderDelta -= passes * this.renderTimeInterval;		
		
		for (var i = 2; i < passes; i++) {
			this.renderer.skipFrame(this.scene);
		}
		
		if (passes) {			
			this.renderer.renderFrame(this.scene);			
		}
		
		if (this.running) Stasis.timeout(this, 1, this.loop);
	}	
	/* end Game */
	
	/* class KeyboardControl */
	this.KeyboardControl = function () {
		var _this = this;
		this.callbacksOn = {};
		this.callbacksOff = {};
		this.keyMap = [];
		
		$(document).keydown(function (e) {			
			if (e.keyCode in _this.keyMap) {
				var key = _this.keyMap[e.keyCode];
				if (!key.state) {
					key.state = true;
					if (key.callback in _this.callbacksOn) {
						_this.callbacksOn[key.callback].fire();
					}
				}
			}
		}).keyup(function (e) {			
			if (e.keyCode in _this.keyMap) {
				var key = _this.keyMap[e.keyCode];
				if (key.state) {
					key.state = false;
					if (key.callback in _this.callbacksOff) {						
						_this.callbacksOff[key.callback].fire();
					}
				}
			}
		});
	}
	
	this.KeyboardControl.prototype.listen = function (keyCode, name) {
		if (!(name in this.callbacksOn)) {
			this.callbacksOn[name] = new $.Callbacks();
		}
		if (!(name in this.callbacksOff)) {
			this.callbacksOff[name] = new $.Callbacks();
		}
		this.keyMap[keyCode] = { 'callback': name, 'state': false }
		return this;
	}	
	/* end KeyboardControl */
	
} (jQuery);/* end Stasis */

////////////////////////////////////////////////////////////////////////

Application = new function () { 
	
	this.windowLoaded = new $.Deferred();	
	
	var version = '1.1.0',
		repos = [ 'http://tower100.no-ip.org/GlitchTools' ];
		
	this.checkVersion = function () {		
		var count = repos.length, reposData = [], calls = [];
		
		function newer(current, other) {			
			var _a = current.split(/\./),
				_b = other.split(/\./),
				c = Math.max(_a.length, _b.length);
				
			for (var i = 0; i < c; i++) {				
				if (!_a[i]) _a[i] = 0;
				if (!_b[i]) _b[i] = 0;
				_a[i] = parseInt(_a[i]);
				_b[i] = parseInt(_b[i]);				
				if (_b[i] > _a[i]) {
					return true;
				}
			}
			return false;
		}
		
		Stasis.each(this, repos, function (id, url) {
			calls[id] = $.ajax({
				'crossDomain': true,
				'url': url + '/latest', 
				'dataType': 'json',
				'success': function (data, textStatus, jqXHR) {
					reposData[id] = data;
					DEBUG && console.log(url + ' - ' + data.version);
				},
				'error': function (jqXHR, textStatus, errorThrown) {
					DEBUG && console.error(url, textStatus, errorThrown);
				}				
			});
		});
		
		var best = version, bestLocation = null;
		
		var display = function () {
			
			Stasis.each(this, reposData, function (id, data) {
				if (newer(best, data.version)) {
					best = data.version;
					bestLocation = repos[id] + data.location;
					bestMessage = data.message;
				}
			});
			
			if (bestLocation) {
				Application.flash('<strong>New version found!</strong> ' +
					'(' + best + ') ' + bestMessage);
			}
		}
		
		$.when.apply(this, calls).then(display, display);
	}	
	
	this.flash = function (message) {
		var flash = $('#flash');
		this.windowLoaded.then(function () {
			flash.empty().append(message).append(
			$('<button class="dismiss">Dismiss</button>')
				.click(function () {
					$('#flash').hide(600);
				})
			).show();
		});
		return flash;		
	}	
	
	this.downloadImage = function (url) {
		var deferred = new $.Deferred();
		deferred.image = new Image();							
		deferred.image.onload = function () { 			
			deferred.resolve(url); 
		};				
		deferred.image.onerror = function () { 
			console.error('failed to get ' + url);
			deferred.reject(url);
			// todo: error handling
		}; 
		deferred.image.src = url;			
		return deferred;
	}
	
	this.getGlitchen = function(tsid) {
		$('#tsid').html(tsid);
		$.ajax({
			'crossDomain': true,
			'url' : 'http://api.glitch.com/simple/players.getAnimations',
			'dataType' : 'json',
			'data' : { 'player_tsid' : tsid },
			'success' : function(data, textStatus, jqXHR){
				
				if (!data.ok) {
					alert(data.error);
					return;
				}
				
				var downloads = {};
				var downloads_list = [];
				var imageCache = $('#images');
				
				for(var id in data.sheets) {
					var sheet = data.sheets[id];
					sheet.id = 'i-' + id;							
					downloads[id] = Application.downloadImage(sheet.url);
					downloads_list.push(downloads[id]);
					imageCache.append($('<img/>').attr({ 'src': sheet.url, 'id': sheet.id }));
				}
										
				$('#fuse').hide();												
				$.when.apply(this, downloads_list).then(function () {
					for(var id in data.sheets) {
						var sheet = data.sheets[id];
						sheet.width = downloads[id].image.width;
						sheet.height = downloads[id].image.height;				
					}
					$('#data').html(JSON.stringify(data));
					Application.start();
				});						
			},
			'error' : function(jqXHR, textStatus, errorThrown){						
				alert(errorThrown);
			}
		});
	}
	
	this.devControl = function(glitchen) {
		var controls = $('#controls');	
			
		var i = 0;
		var group;
		
		Application.currentAnim = glitchen.getSequence('idle0');
		
		var slider = $('<input/>').attr({'type': 'range', 'min': 0, 'max': Application.currentAnim.length - 1})
			.width(600)
			.change(function () {
				var val = parseInt($(this).val());		
				var seq = [Application.currentAnim[val]];				
				glitchen.setLoop(seq);
				glitchen.playSequence(seq);
				$('#frameNumber').val(val);
			});
		
		$.each(glitchen.frameSet.animations, function (id, anim) {

			if (++i % 3 == 0) {
				group = $('<div/>').addClass('controlGroup');
				group.appendTo(controls);
			}		
			var animCheck = $('<input/>').addClass('controlButton').attr({ 'type': 'button', 'name': 'anim', 'id': 'anim', 'value': id });		
			animCheck.appendTo(group)
				.click(function (e) {	
					glitchen.setLoop('idle');
					$('.controlButton').removeClass('controlButtonToggled');
					$(this).addClass('controlButtonToggled');
					Application.currentAnim = anim;				
					slider.attr('max', anim.length - 1);					
					glitchen.playSequence(anim);					
				});
		});
		
		controls.append($('<div style="padding-bottom: 40px"/>').append(slider).append($('<input id="frameNumber" readonly="readonly"/>')));
	}
	
	this.start = function () {
		$('head').append('<link rel="stylesheet" href="./Glitchen_files/stasis.css" type="text/css" />');
		
		$('#view').empty()			
			.append('<div id="controls"></div>')
			.append('<div class="wrap" id="canvas-wrap"></div>')
					
		$('#fuse').empty()
			.append($('<div class="wrap"/>')
				.append('<input type="text" id="enter-tsid" class="rounded" /><br/>')
				.append('<button id="get-glitchen">Get me Glithen!</button>')				
			);
			
		$('#extra').remove();	
		$('#storage').remove();
		$('body').append('<div id="storage"/>').append($('<div id="extra"/>')
			.append($('<div id="footer-wrap"/>')				
				.append($('<div id="footer"/>')
					.append('<div id="flash" class="rounded" /></div>')					
				)));	
				
		$('#controls').empty();		
		
		$('#enter-tsid').keyup(function (e) { if (e.keyCode == 13) $('#get-glitchen').click() });
		$('#get-glitchen').click(function () { Application.getGlitchen($('#enter-tsid').val()) });		
				
		if (!$('#fuse').is(':visible')) {
			
			Application.checkVersion();
			
			$('#cache').addClass('glitchen-').find('*').each(function () {				
				$(this).addClass($(this).attr('id'));
			});
			
			var surface = Stasis.createSurface(800, DEBUG ? 300 : 600);
			$(surface.canvas).width(surface.canvas.width).height(surface.canvas.height).appendTo($('#canvas-wrap'));			
			
			var renderer = new Stasis.Renderer(surface);			
			
			var glitchen = new Stasis.Glitchen($('.glitchen-'));
			glitchen.y = 50;				
			
			var scene = [new Stasis.StaticImage('Glitchen_files/background.jpg').save($('#storage')), glitchen];
			
			var game = new Stasis.Game(scene, renderer, 30, 30);
			game.dependOn.apply(game, scene);
			game.start();		
			
			var kb = new Stasis.KeyboardControl();
			Stasis.each(this, keyMap, function(key, name) {
				kb.listen(key, name);
			});
			
			kb.callbacksOn['left'].add(function () { glitchen.walkLeft() });
			kb.callbacksOn['right'].add(function () { glitchen.walkRight() });
			kb.callbacksOn['space'].add(function () { glitchen.jump() });
			kb.callbacksOn['1'].add(function () { glitchen.laugh() });
			kb.callbacksOn['2'].add(function () { glitchen.rage() });
			kb.callbacksOn['3'].add(function () { glitchen.wat() });
			kb.callbacksOn['s'].add(function () { glitchen.sleep() });
			
			kb.callbacksOff['left'].add(function () { glitchen.walkStop() });
			kb.callbacksOff['right'].add(function () { glitchen.walkStop() });	
			
				
			
			window.glitchen = glitchen;
			window.scene = scene;
			
			DEBUG && this.devControl(glitchen);			
		}		
	}
	
	this.run = function () {				
		$(window).bind('load', function () { Application.windowLoaded.resolve(); });
		$(document).ready(function () { Application.start(); });
	}	
}
	
Application.run();
